﻿using System;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Stonecode.Solar.ParticleEngine.Emitters;
using System.ComponentModel;
using System.Globalization;

namespace Stonecode.Solar.ParticleEngine
{
    public enum KnownBlendOptions
    {
        Additive,
        Subtractive,
        AlphaBlend,
        Opaque,
        NonPremultiplied
    };

    /// <summary>
    /// Settings struct describes all the tweakable options used
    /// to control the appearance of a particle system.
    /// </summary>
    public class EmitterSettings
    {
        // Name of the texture used by this particle system.
        public string TextureName = null;

        public string EmitterName;
        
        // Maximum number of particles that can be displayed at one time.
        // This should only be set by the constructor since this is the 
        // size of the array

        private uint _maxParticles = 100;
        public uint MaxParticles { get { return _maxParticles; } }

        public float ParticlesPerSecond { get; set; }

        protected Vector3 _offset;
#if WINDOWS
        [System.ComponentModel.Category("Position")]
        [System.ComponentModel.Description("The offset from the emitter position in which it fires")]
#endif
        public Vector3 Offset { get { return _offset; } set { _offset = value; } }

        // How long these particles will last.
#if WINDOWS
        [System.ComponentModel.Category("Life")]
#endif
        public TimeSpan Duration { get; set; }

        public bool Enabled { get; set; }
        
        // If greater than zero, some particles will last a shorter time than others.
#if WINDOWS
        [Category("Life")]
#endif
        public float DurationRandomness { get; set; }


        // Controls how much particles are influenced by the velocity of the object
        // which created them. You can see this in action with the explosion effect,
        // where the flames continue to move in the same direction as the source
        // projectile. The projectile trail particles, on the other hand, set this
        // value very low so they are less affected by the velocity of the projectile.
#if WINDOWS
        [Category("Velocity")]
#endif
        public float EmitterVelocitySensitivity { get; set; }


        // Range of values controlling how much X and Z axis velocity to give each
        // particle. Values for individual particles are randomly chosen from somewhere
        // between these limits.
        private Vector2 _minVelocity;
        private Vector2 _maxVelocity;

        public Vector2 MinVelocity { get { return _minVelocity; } }
        public Vector2 MaxVelocity { get { return _maxVelocity; } }

#if WINDOWS
        [Category("Velocity")]
#endif
        public float MinHorizontalVelocity 
        {
            get { return _minVelocity.X; }
            set
            {
                if (_minVelocity == null)
                    _minVelocity = Vector2.Zero;

                _minVelocity.X = value;
            }
        }
#if WINDOWS
        [Category("Velocity")]
#endif
        public float MaxHorizontalVelocity 
        {
            get { return _maxVelocity.X; }
            set
            {
                if (_maxVelocity == null)
                    _maxVelocity = Vector2.Zero;

                _maxVelocity.X = value;
            } 
        }

        // Range of values controlling how much Y axis velocity to give each particle.
        // Values for individual particles are randomly chosen from somewhere between
        // these limits.
#if WINDOWS
        [Category("Velocity")]
#endif
        public float MinVerticalVelocity
        {
            get { return _minVelocity.Y; }
            set
            {
                if (_minVelocity == null)
                    _maxVelocity = Vector2.Zero;

                _minVelocity.Y = value;
            } 
        }
#if WINDOWS
        [Category("Velocity")]
#endif
        public float MaxVerticalVelocity
        {
            get { return _maxVelocity.Y; }
            set
            {
                if (_maxVelocity == null)
                    _maxVelocity = Vector2.Zero;

                _maxVelocity.Y = value;
            }
        }

        // Controls how the particle velocity will change over their lifetime. If set
        // to 1, particles will keep going at the same speed as when they were created.
        // If set to 0, particles will come to a complete stop right before they die.
        // Values greater than 1 make the particles speed up over time.
#if WINDOWS
        [Category("Velocity")]
#endif
        public float EndVelocity { get; set; }


        // Range of values controlling the particle color and alpha. Values for
        // individual particles are randomly chosen from somewhere between these limits.
#if WINDOWS
        [Category("Color")]
        [EditorAttribute(typeof(System.Windows.Forms.ColorDialog), typeof(System.Drawing.Design.ColorEditor))]
#endif
        public Color MinColor { get; set; }

#if WINDOWS
        [Category("Color")]
        [Editor(typeof(System.Windows.Forms.ColorDialog), typeof(System.Drawing.Design.ColorEditor))]
#endif
        public Color MaxColor { get; set; }

        private Vector2 _fadeTime = Vector2.Zero;
        public float FadeIn { get; set; }
        public float FadeOut { get; set; }

        public Vector2 FadeTime
        {
            get
            {
                _fadeTime.X = FadeIn;
                _fadeTime.Y = FadeOut;
                return _fadeTime;
            }
        }

        public float FadeMultiplierIn { get; set; }
        public float FadeMultiplierOut { get; set; }
        private Vector2 _fadeMult = Vector2.One;
        public Vector2 FadeMultiplier
        {
            get
            {
                _fadeMult.X = FadeMultiplierIn;
                _fadeMult.Y = FadeMultiplierOut;
                return _fadeMult;
            }
        }
        // Range of values controlling how fast the particles rotate. Values for
        // individual particles are randomly chosen from somewhere between these
        // limits. If both these values are set to 0, the particle system will
        // automatically switch to an alternative shader technique that does not
        // support rotation, and thus requires significantly less GPU power. This
        // means if you don't need the rotation effect, you may get a performance
        // boost from leaving these values at 0.
#if WINDOWS
        [Category("Rotation")]
#endif
        public float MinRotateSpeed { get; set; }
#if WINDOWS
        [Category("Rotation")]
#endif
        public float MaxRotateSpeed { get; set; }

#if WINDOWS
        [Category("Rotation")]
#endif
        public int RotationVariation { get; set; }


        // Range of values controlling how big the particles are when first created.
        // Values for individual particles are randomly chosen from somewhere between
        // these limits.
#if WINDOWS
        [Category("Size")]
#endif
        public float MinStartSize { get; set; }
#if WINDOWS
        [Category("Size")]
#endif
        public float MaxStartSize { get; set; }


        // Range of values controlling how big particles become at the end of their
        // life. Values for individual particles are randomly chosen from somewhere
        // between these limits.
#if WINDOWS
        [Category("Size")]
#endif
        public float MinEndSize { get; set; }
#if WINDOWS
        [Category("Size")]
#endif
        public float MaxEndSize { get; set; }


        // Color Blend Options
#if WINDOWS
        [Category("Blend Options Color")]
#endif
        public Blend ColorSourceBlend { get; set; }
#if WINDOWS
        [Category("Blend Options Color")]
#endif
        public Blend ColorDestinationBlend { get; set; }

#if WINDOWS
        [Category("Blend Options Color")]
#endif
        public BlendFunction ColorBlendFunction { get; set; }


        // Alpha blending settings.
#if WINDOWS
        [Category("Blend Options Alpha")]
#endif
        public Blend AlphaSourceBlend { get; set; }

#if WINDOWS
        [Category("Blend Options Alpha")]
#endif
        public Blend AlphaDestinationBlend { get; set; }

#if WINDOWS
        [Category("Blend Options Alpha")]
#endif
        public BlendFunction AlphaBlendFunction { get; set; }

#if WINDOWS
        [Category("Blend Options Alpha")]
#endif
        public Color BlendFactor { get; set; }

        private BlendState _blendState;

#if WINDOWS
        [Category("Blend Options")]
#endif
        public BlendState BlendState { get { return _blendState; } }


        #region Forces

        // Direction and strength of the gravity effect. Note that this can point in any
        // direction, not just down! The fire effect points it upward to make the flames
        // rise, and the smoke plume points it sideways to simulate wind.
        private Vector3 _gravity;
        public Vector3 Gravity
        {
            get { return _gravity; }
            set
            {
                LastGravity = _gravity;
                _gravity = value;
            }
        }
        public Vector3 LastGravity { get; set; }

        public bool CheckGravityChange () { return !(_gravity == LastGravity); }

        public Vector3 BlackHole_Position { get; set; }
        private float _blackholeStrength;
        public float BlackHole_Strength 
        {
            get { return _blackholeStrength; }
            set
            {
                LastBlackHole_Strength = _blackholeStrength;
                _blackholeStrength = value;
            }
        }
        public float LastBlackHole_Strength { get; set; }
        
        public bool CheckBlackholeChange() { return !(_blackholeStrength == LastBlackHole_Strength); }

        private float _radialGravity;
        public float RadialGravity
        {
            get { return _radialGravity; }
            set
            {
                LastRadialGravity = _radialGravity;
                _radialGravity = value;
            }
        }
        public float LastRadialGravity { get; set; }

        public bool CheckRadialGravityChange() { return !(_radialGravity == LastRadialGravity); }

        #endregion





        public EmitterSettings(uint maxParticles)
        {
            _maxParticles = maxParticles;
            Duration = TimeSpan.FromSeconds(1);
            DurationRandomness = 0;
            EmitterVelocitySensitivity = 1;
            MinHorizontalVelocity = -10;
            MaxHorizontalVelocity = 10;
            MinVerticalVelocity = -10;
            MaxVerticalVelocity = 10;
            Gravity = Vector3.Zero;
            EndVelocity = 1;
            MinColor = Color.White;
            MaxColor = Color.White;
            MinRotateSpeed = 0;
            MaxRotateSpeed = 0;
            MinStartSize = 2;
            MaxStartSize = 2;
            MinEndSize = 2;
            MaxEndSize = 2;
            
            _blendState = new BlendState();
            ImportBlendOptions(BlendState.Additive);

            FadeIn = 0;
            FadeOut = 0;
            _fadeTime = new Vector2(FadeIn, FadeOut);
            FadeMultiplierIn = 1;
            FadeMultiplierOut = 1;
            _fadeMult = new Vector2(FadeMultiplierIn, FadeMultiplierOut);

        }
        public EmitterSettings()
        {
            _maxParticles = 1000;
            Duration = TimeSpan.FromSeconds(1);
            DurationRandomness = 0;
            EmitterVelocitySensitivity = 1;
            MinHorizontalVelocity = -10;
            MaxHorizontalVelocity = 10;
            MinVerticalVelocity = -10;
            MaxVerticalVelocity = 10;
            Gravity = Vector3.Zero;
            EndVelocity = 1;
            MinColor = Color.White;
            MaxColor = Color.White;
            MinRotateSpeed = 0;
            MaxRotateSpeed = 0;
            MinStartSize = 2;
            MaxStartSize = 2;
            MinEndSize = 2;
            MaxEndSize = 2;
            _blendState = new BlendState();
            ImportBlendOptions(BlendState.Additive);
        }

        public void ApplyBlendOptions()
        {
            if (_blendState.GraphicsDevice != null)
                _blendState = new Microsoft.Xna.Framework.Graphics.BlendState();

                _blendState.AlphaSourceBlend = AlphaSourceBlend;
                _blendState.AlphaDestinationBlend = AlphaDestinationBlend;
                _blendState.AlphaBlendFunction = AlphaBlendFunction;

                _blendState.BlendFactor = BlendFactor;
                _blendState.ColorBlendFunction = ColorBlendFunction;
                _blendState.ColorDestinationBlend = ColorDestinationBlend;
                _blendState.ColorSourceBlend = ColorSourceBlend; 
        }

        public void ImportBlendOptions(BlendState bState)
        {
            AlphaSourceBlend = bState.AlphaSourceBlend;
            AlphaDestinationBlend = bState.AlphaDestinationBlend;
            AlphaBlendFunction = bState.AlphaBlendFunction;

            BlendFactor = bState.BlendFactor;

            ColorBlendFunction = bState.ColorBlendFunction;
            ColorDestinationBlend = bState.ColorDestinationBlend;
            ColorSourceBlend = bState.ColorSourceBlend;

            ApplyBlendOptions();
        }

        public void SetKnownBlendOptions(KnownBlendOptions kbo)
        {
            switch (kbo)
            {
                case KnownBlendOptions.Additive: ImportBlendOptions(BlendState.Additive); break;
                case KnownBlendOptions.AlphaBlend: ImportBlendOptions(BlendState.AlphaBlend); break;
                case KnownBlendOptions.NonPremultiplied: ImportBlendOptions(BlendState.NonPremultiplied); break;
                case KnownBlendOptions.Opaque: ImportBlendOptions(BlendState.Opaque); break;
                case KnownBlendOptions.Subtractive:
                    {
                        AlphaSourceBlend = Blend.SourceAlpha;
                        AlphaDestinationBlend = Blend.Zero;
                        AlphaBlendFunction = BlendFunction.Add;
                        ColorBlendFunction = BlendFunction.ReverseSubtract;
                        ColorDestinationBlend = Blend.InverseSourceAlpha;
                        ColorSourceBlend = Blend.SourceColor;
                        BlendFactor = Color.White;

                        ApplyBlendOptions();
                        // TODO: set the blend states to remove the color from the screen...
                        break;
                    }
            }
        }
    }
}
